import 'dart:async';
import 'dart:math' as math;

import 'package:bot_toast/bot_toast.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

enum HUDMode {
  toast,
  indeterminate,
  determinate,
  info,
  success,
  warning,
  error,
}

typedef HUDControllerConfigBlock = HUDController Function(
    HUDController controller);

typedef CloseHUDFunction = void Function();

class HUDController extends ValueNotifier<double> {
  HUDController(
    HUDMode mode, {
    String? text,
    double progress = 0.0,
    this.alignment = Alignment.center,
  }) : super(progress) {
    _mode = mode;
    _text = text;
    _bottom = bottom;

    reload();
  }

  void reload() {
    if (this.withoutDotText != null) {
      _text = '${this.withoutDotText}…';
    } else if (text == null &&
        (this.mode == HUDMode.indeterminate ||
            this.mode == HUDMode.determinate)) {
      _text = '加载中…';
    }

    if (this.mode == HUDMode.indeterminate) {
      if (this.progress == 0.0) {
        this.progress = HUDStyle.indeterminateProgress;
      }
    }
  }

  /// =========仅在第一次显示前配置有效=========
  bool clickClose = false;
  bool allowClick = false;
  bool crossPage = false;
  bool onlyOne = false;

  /// =====================================

  late HUDMode _mode;

  HUDMode get mode => this._mode;

  set mode(HUDMode mode) {
    this._mode = mode;
    notifyListeners();
  }

  double get progress => this.value;

  set progress(double progress) {
    if (progress >= 1.0) {
      progress = 1.0;
    }
    if (value != progress) {
      this.value = progress;
      notifyListeners();
    }
  }

  Widget? _indeterminateProgressWidget;

  Widget? get indeterminateProgressWidget => _indeterminateProgressWidget;

  set indeterminateProgressWidget(Widget? indeterminateProgressWidget) {
    if (_indeterminateProgressWidget != indeterminateProgressWidget) {
      this._indeterminateProgressWidget = indeterminateProgressWidget;
      notifyListeners();
    }
  }

  final Alignment alignment;

  String? _text;

  String? get text => this._text;

  set text(String? text) {
    if (_text != text) {
      _text = text;
      if (_text != null) {
        _withoutDotText = null;
      }
      notifyListeners();
    }
  }

  String? _withoutDotText;

  String? get withoutDotText => this._withoutDotText;

  set withoutDotText(String? withoutDotText) {
    if (_withoutDotText != withoutDotText) {
      _withoutDotText = withoutDotText;
      if (_withoutDotText != null) {
        _text = null;
      }
      notifyListeners();
    }
  }

  Widget? _bottom;

  Widget? get bottom => this._bottom;

  set bottom(Widget? bottom) {
    this._bottom = bottom;
    notifyListeners();
  }

  double? _contentWidth;

  double? get contentWidth => this._contentWidth;

  set contentWidth(double? contentWidth) {
    this._contentWidth = contentWidth;
    notifyListeners();
  }

  CancelFunc? _cancelFunc;
  CloseHUDFunction? closeHUDFunction;

  void showHUD({bool dimBackground = false, Duration? duration}) {
    _cancelFunc = BotToast.showEnhancedWidget(
      clickClose: this.clickClose,
      allowClick: this.allowClick,
      onlyOne: this.onlyOne,
      crossPage: this.crossPage,
      backgroundColor:
          dimBackground ? Colors.black.withOpacity(0.3) : Colors.transparent,
      toastBuilder: (cancelFunc) {
        return HUDContent(controller: this);
      },
      duration: null,
    );

    closeHUDFunction = () {
      _cancelFunc?.call();
    };

    //定时功能
    Timer? timer;
    if (duration != null) {
      timer = Timer(duration, () {
        timer!.cancel();
        timer = null;
        hideHUD();
      });
    }
  }

  void switchToIndeterminateMode({String? text, String? withoutDotText}) {
    _mode = HUDMode.indeterminate;
    _text = text;
    this.withoutDotText = withoutDotText;
    this.progress = 0.0;
    reload();
    notifyListeners();
  }

  void hideHUD() {
    closeHUDFunction?.call();
    _doHideCallback();
  }

  @override
  void notifyListeners() {
    reload();
    super.notifyListeners();
  }

  final _hideCallback = <VoidCallback>[];

  void addHideCallback(VoidCallback callback) {
    _hideCallback.add(callback);
  }

  void _doHideCallback() {
    _hideCallback.forEach((element) {
      element();
    });
    _hideCallback.clear();
  }
}

class HUDStyle {
  static const double $defaultDurationTag = 99999.1;
  static double defaultDuration = 2.0;

  static Alignment defaultToastAlignment = Alignment(0.0, -0.7);
  static Alignment defaultHUDAlignment = Alignment.center;

  static double indeterminateProgress = 0.25;
}

class HUDContent extends StatefulWidget {
  const HUDContent({Key? key, required this.controller}) : super(key: key);

  final HUDController controller;

  @override
  _HUDContentState createState() => _HUDContentState();
}

class _HUDContentState extends State<HUDContent> with TickerProviderStateMixin {
  late var _listener;

  Widget? _circle;
  Widget? icon;
  double? _circleRadius;
  AnimationController? _rotateController;

  @override
  void initState() {
    _listener = () {
      if (mounted) {
        setState(() {
          buildContents();
        });
      }
    };

    buildContents();
    widget.controller.addListener(_listener);

    super.initState();
  }

  @override
  void dispose() {
    widget.controller.removeListener(_listener);
    _checkRotateAnimationController(true);
    super.dispose();
  }

  void _checkRotateAnimationController(bool dispose) {
    _rotateController?.dispose();
    _rotateController = null;
    if (dispose == false && widget.controller.mode == HUDMode.indeterminate) {
      _rotateController = AnimationController(
          duration: const Duration(milliseconds: 700), vsync: this);
      _rotateController!.repeat();
    }
  }

  void buildContents() {
    HUDMode currentMode = widget.controller.mode;

    _rotateController?.dispose();
    _rotateController = null;

    if (currentMode == HUDMode.indeterminate) {
      _checkRotateAnimationController(false);
    }

    _circle = null;

    if (currentMode == HUDMode.toast) {
      icon = null;
    } else if (currentMode == HUDMode.indeterminate ||
        currentMode == HUDMode.determinate) {
      _circleRadius =
          /*
          currentMode == HUDMode.indeterminate ? 30.0 : */
          35.0;
      double _sweepAngle = widget.controller.progress * 360.0;
      _circle = CustomPaint(
        isComplex: false,
        painter: _CircleProgressBarPainter(
            Colors.white, Color(0xFF3BD060), 0, _sweepAngle, 360, 2.5),
        size: Size(_circleRadius!, _circleRadius!),
      );

      // web旋转不规则，暂时不做RepaintBoundary
      if (!kIsWeb && currentMode == HUDMode.indeterminate) {
        _circle = RepaintBoundary(
          child: _circle,
        );
      }
    } else {
      late Widget image;
      double fontSize = 35.0;
      if (currentMode == HUDMode.info) {
        image = Icon(
          CupertinoIcons.info,
          size: fontSize,
          color: Color(0xFFF89426),
        );
      } else if (currentMode == HUDMode.success) {
        image = Icon(
          CupertinoIcons.check_mark_circled,
          size: fontSize,
          color: Color(0xFF3BD060),
        );
      } else if (currentMode == HUDMode.warning) {
        image = Icon(
          CupertinoIcons.info,
          size: fontSize,
          color: Color(0xFFF65558),
        );
      } else if (currentMode == HUDMode.error) {
        image = Icon(
          CupertinoIcons.clear_circled,
          size: fontSize,
          color: Color(0xFFF65558),
        );
      }

      icon = Container(
        width: 40.0,
        height: 40.0,
        child: Center(
          child: image,
        ),
      );
    }
  }

  Widget _circleContent() {
    if (widget.controller.mode == HUDMode.indeterminate) {
      _checkRotateAnimationController(false);
      final child = RotationTransition(
        alignment: Alignment.center,
        turns: _rotateController!,
        child: _circle,
      );

      if (widget.controller.indeterminateProgressWidget == null) {
        return child;
      }

      return Stack(
        alignment: Alignment.center,
        children: [
          child,
          widget.controller.indeterminateProgressWidget!,
        ],
      );
    } else {
      List<Widget> children = [];
      [
        _circle,
        Text(
          '${(widget.controller.progress * 100).toInt()}% ',
          textAlign: TextAlign.center,
          style: TextStyle(
            fontSize: 12.0,
            color: Colors.white,
            fontWeight: FontWeight.w300,
          ),
        ),
      ].forEach((element) {
        if (element != null) {
          children.add(element);
        }
      });

      return Stack(
        alignment: Alignment.center,
        children: children,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    Widget content = LayoutBuilder(
      builder: (BuildContext context, BoxConstraints constraints) {
        bool isToastHUD = widget.controller.mode == HUDMode.toast;

        BoxDecoration decoration = BoxDecoration(
          borderRadius: BorderRadius.circular(isToastHUD ? 20.0 : 10.0),
          color: Color.fromARGB((0.9 * 255).toInt(), 0, 0, (0.2 * 255).toInt()),
        );

        Widget bottom = (widget.controller.bottom ??
            Text(
              widget.controller.text ?? '',
              textAlign: TextAlign.center,
              style: TextStyle(
                fontSize: isToastHUD ? 15.0 : 12.0,
                fontWeight: FontWeight.normal,
                color: Colors.white,
                height: 1.5,
              ),
            ));

        if (isToastHUD) {
          return Container(
            padding: EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
            decoration: decoration,
            width: widget.controller.contentWidth,
            child: ConstrainedBox(
              constraints: BoxConstraints(
                maxWidth: constraints.maxWidth - 30.0 * 2,
              ),
              child: bottom,
            ),
          );
        }

        final double minWidth = _circleRadius ?? 40.0 + 20.0;

        List<Widget> children = [];
        [
          _circle != null ? _circleContent() : icon,
          SizedBox(height: 12.0),
          bottom
        ].forEach((element) {
          if (element != null) {
            children.add(element);
          }
        });

        return Container(
          padding: EdgeInsets.all(20.0),
          width: widget.controller.contentWidth,
          child: ConstrainedBox(
            constraints: BoxConstraints(
              maxWidth: constraints.maxWidth - 30.0 * 2,
              minWidth: minWidth,
              minHeight: minWidth,
            ),
            child: Container(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: children,
              ),
            ),
          ),
          decoration: decoration,
        );
      },
    );

    return Align(
      alignment: widget.controller.alignment,
      child: content,
    );
  }
}

class _CircleProgressBarPainter extends CustomPainter {
  var _paintBckGround;
  var _paintFore;

  final _strokeWidth;
  final _backgroundColor;
  final _foreColor;
  final _startAngle;
  final _sweepAngle;
  final _endAngle;

  _CircleProgressBarPainter(this._backgroundColor, this._foreColor,
      this._startAngle, this._sweepAngle, this._endAngle, this._strokeWidth) {
    _paintBckGround = Paint()
      ..color = _backgroundColor
      ..isAntiAlias = true
      ..strokeCap = StrokeCap.round
      ..strokeWidth = _strokeWidth
      ..style = PaintingStyle.stroke;

    _paintFore = Paint()
      ..color = _foreColor
      ..isAntiAlias = true
      ..strokeWidth = _strokeWidth
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke;
  }

  final double fixAngle = 90.0;

  @override
  void paint(Canvas canvas, Size size) {
    var radius = size.width > size.height ? size.width / 2 : size.height / 2;
    Rect rect = Rect.fromCircle(center: Offset(radius, radius), radius: radius);

    canvas.drawCircle(Offset(radius, radius), radius, _paintBckGround);

    canvas.drawArc(rect, radians(_startAngle - fixAngle), radians(_sweepAngle),
        false, _paintFore);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return _sweepAngle != _endAngle;
  }

  double radians(double deg) => deg * (math.pi / 180.0);
}
